/* 
 * File:   ProcesoAutenticacion.cpp
 * Author: maximiliano
 * 
 * Created on 7 de junio de 2013, 18:50
 */

#include "server_ProcesoAutenticacion.h"
#include "server_Sesion.h"
#include "server_UsersDB.h"
#include "common_MutexScopeLocker.h"
#include "common_Descriptor.h"
#include "common_md5.h"

using namespace TP;
using namespace TP::Procesos;

ProcesoAutenticacion::ProcesoAutenticacion(const uint32_t &pId
                                         , IAutenticacionManejadorEvento* pManejadorEvento
                                         , const string& pUsuariosDBRutaARchivo) 
: Proceso (pId) 
{
    _autenticacionManejadorEvento = pManejadorEvento;
    _usuariosDBRutaARchivo = pUsuariosDBRutaARchivo;
    _usuariosDB = NULL;
}

ProcesoAutenticacion::~ProcesoAutenticacion(){
    
    _autenticacionManejadorEvento = NULL;
    
    if (NULL != _usuariosDB)
    {
        delete _usuariosDB;
    }
}

void ProcesoAutenticacion::eventoNuevaSesion (IEnviante* pEnviante, void* pArg) {
    
    // pEnviante es NULL si el mensaje proviene del este proceso.
    
    PushSesion((Sesion*) pArg);
}

void ProcesoAutenticacion::eventoErrorConexionInexistente (IEnviante* pEnviante, const string& pMensaje){
    
    /* Se produjo un error de conexion.  Se descartan las sesiones en curso y
     * se finaliza el proceso.
     */ 
    this->SetCorriendo(false);
    DescartarSesiones();
}

void ProcesoAutenticacion::tarea(){
    
    /* La tarea del proceso de autenticacion consiste en verificar la autenticacion
     * del cliente al que corresponde la sesion utilizando las credenciales que 
     * proporciona.  En caso de ser un cliente valido, notifica la sesion al 
     * manejador de eventos de autenticacion.  Caso contrario finaliza la sesion.
     * 
     */
    
    while (GetCorriendo())
    {
        // Tomar una sesion de la cola de sesiones.
        
        Sesion* sesion = PopSesion();
        if (NULL == sesion) continue;
        
        if (sesion->GetFinalizada())
        {
            sesion->Iniciar();
        }
        
        // Verificar autenticacion.

        string usuario = ""
             , contrasenia = "";
        
        GetCredencial (usuario, contrasenia, sesion);
        
        if ("" == usuario)
        {
            // No obtuvo la credencial.  Se vuelve a encolar la sesion.
            this->eventoNuevaSesion(NULL, sesion);
        }
        else
        {
            // Obtuvo la credencial correctamente.
            bool autenticado = _usuariosDB->AutenticarUsuario(usuario, contrasenia);

            if (autenticado){

                // Se configura la sesion como autenticada.

                sesion->SetAutenticada(true);
                sesion->SetUsuario(usuario);
                
                // Se notifica la existencia de la nueva sesion.
                _autenticacionManejadorEvento->notificarNuevaSesionEvento(sesion);

                // Se informa al cliente sobre el estadod e autenticacion.
                sesion->Responder(ConstruirRespuestaAutenticacion(AUTENTICACION_OK, sesion->GetUsuario(), "Autenticacion exitosa."));
                
                // Se seguriza la conexion.
                char hashedPsw[33];
                calcularHashMD5Bloque(contrasenia.c_str(), contrasenia.size(), hashedPsw);
                std::string claveFirma(hashedPsw);
        
                sesion->Segurizar(claveFirma);
            }
            else
            {
                // Se notifica la existencia de una sesion con autenticacion fallida.
                _autenticacionManejadorEvento->notificarErrorAutenticacionEvento(sesion);

                // Se notifica al cliente el error de autenticacion.
                sesion->Responder(ConstruirRespuestaAutenticacion(AUTENTICACION_FALLO, sesion->GetUsuario(), "Debe proporcionar una credencial valida."));

                // Se finaliza la sesion (a fin de liberar recursos).
                sesion->Finalizar();
            }
        }
    }
}

Sesion* ProcesoAutenticacion::PopSesion (){
    
    MutexScopeLocker _locker (_sesionesMutex);
    Sesion* sesion = NULL;
    
    if (!_sesiones.empty())
    {
         sesion = _sesiones.front();
        _sesiones.pop();
    }
    
    return sesion;
}

Sesion* ProcesoAutenticacion::PushSesion(Sesion* pSesion){
    
    MutexScopeLocker _locker (_sesionesMutex);
    
    if (NULL != pSesion)
    {
        _sesiones.push(pSesion);
    }
    
    return pSesion;
}

void ProcesoAutenticacion::DescartarSesiones(){
 
    MutexScopeLocker _locker (_sesionesMutex);   
    
    while (!_sesiones.empty())
    {
        _sesiones.pop();
    }
}

void ProcesoAutenticacion::Iniciar() throw (Excepcion&){
    try
    {
        _usuariosDB = new UsersDB(_usuariosDBRutaARchivo);
        Proceso::Iniciar();
    }
    catch (Excepcion &ex)
    {
        throw;
    }
}

void ProcesoAutenticacion::Finalizar() throw (Excepcion&){

    try
    {
        Proceso::Finalizar();
    }
    catch (Excepcion &ex)
    {
        throw;
    }
}

string ProcesoAutenticacion::ConstruirRespuestaAutenticacion(const string &pResultadoAutenticacion, const string &pUsuario, const string& pMensaje ){
    
    string respuesta = pResultadoAutenticacion;
    
    respuesta += SEPARADOR_CAMPO;
    respuesta += pUsuario;
    respuesta += SEPARADOR_CAMPO;
    respuesta += pMensaje;
    
    return respuesta;
}

void ProcesoAutenticacion::GetCredencial (string & pUsuario, string & pContrasenia, Sesion* pSesion){

    // Leo el mensaje de autenticacion de la sesion.
    Descriptor descriptor (SEPARADOR_CAMPO);
    descriptor.parse(pSesion->GetCredencial());
    
    if (USUARIO_CREDENCIAL == descriptor.data(0))
    {
        pUsuario = descriptor.data(1);
        pContrasenia = descriptor.data(2);
    }
}

UsersDB* ProcesoAutenticacion::GetBaseDatosUsuarios(){
    return _usuariosDB;
}